package tcp

import (
	"ams_client/dao"
	"ams_client/global"
	"encoding/json"
	"github.com/sirupsen/logrus"
	"lib/network"
	"lib/structs/ams"
	"lib/utils"
	"strconv"
	"time"
)

type Client struct {
	connId     string      // 连接码，每次连接都会变化
	UniqueCode string      // 设备唯一码，保存在客户端的唯一标识
	ClientId   int64       // 设备ID，保存在服务端的唯一标识
	conn       *ConnStruct // 连接对象
	inCh       chan ams.TcpInstruction
	outCh      chan ams.TcpResponse

	tryCh    chan string
	lastTime int64 // 上次网络变化的时间
}

func InitAmsClient() (c *Client) {
	c = &Client{
		//connId: id.GetUuidV4(),
		inCh:  make(chan ams.TcpInstruction, 100),
		outCh: make(chan ams.TcpResponse, 100),
		tryCh: make(chan string),
	}
	c.conn = NewTCPClient(ams.TcpBackOff, ams.TcpBackOffMax, global.Conf.Tcp.Servers, c.tryCh)
	go c.readLoop()
	go c.writeLoop()
	go c.handMsg()
	go c.networkChange()
	return
}

func (c *Client) handMsg() string {
	for {
		in := <-c.inCh
		logrus.Debugf("client 收到信息：%+v", in)
		switch in.InsCode {
		case ams.InsCode.SetHandsUpArgus:
			if dao.HandsUpArgus != in.InsContent {
				dao.HandsUpArgus = in.InsContent
				// 更新数据库
			}
		case ams.InsCode.SetGuarderArgus:
			if dao.GuarderArgus != in.InsContent {
				dao.GuarderArgus = in.InsContent
				// 更新数据库
			}
		case ams.InsCode.SetFileServerAddr:
			if dao.FileServerAddr != in.InsContent {
				dao.FileServerAddr = in.InsContent
				// 更新数据库
			}
		case ams.InsCode.SetId:
			id, err := strconv.ParseInt(in.InsContent, 10, 64)
			if err != nil {
				logrus.Errorf("SetId err:[%v],in:[%v]", err, in)
				continue
			}
			if c.ClientId != id {
				c.ClientId = id
			}
		case ams.InsCode.UpdatePolicy:
			err := Handler.UpdatePolicy(in)
			if err != nil {
				logrus.Errorf("UpdatePolicy err:[%v],in:[%v]", err, in)
				continue
			}
		case ams.InsCode.ExecutePlugin:
			stdout, err := Handler.ExecPlugin(in)
			if err != nil {
				logrus.Errorf("UpdatePlugin err:[%v],in:[%v]", err, in)
				c.Writer(ams.TcpResponse{
					ResCode:    ams.ResCode.ExecutePlugin,
					ResContent: err.Error(),
				})
				continue
			}
			c.Writer(ams.TcpResponse{
				ResCode:    ams.ResCode.ExecutePlugin,
				ResContent: stdout,
			})
		case ams.InsCode.Ack:
			err := Handler.Ack(in)
			if err != nil {
				logrus.Errorf("Ack err:[%v],in:[%v]", err, in)
				continue
			}
		case ams.InsCode.SaveConfig:
			err := Handler.SaveConfig(in)
			if err != nil {
				logrus.Errorf("SaveConfig err:[%v],in:[%v]", err, in)
				continue
			}
		case ams.InsCode.ExecSql:
			err := Handler.ExecSql(in)
			if err != nil {
				logrus.Errorf("SaveConfig err:[%v],in:[%v]", err, in)
				continue
			}
		}
	}
}

func (c *Client) writeLoop() {
	for {
		in := <-c.outCh
		bs, err := network.Encode(in)
		if err != nil {
			continue
		}
		err = c.conn.Send(bs)
		if err != nil {
			continue
		}
	}
}

func (c *Client) readLoop() {
	for {
		bs, err := c.conn.Read()
		if err != nil {
			continue
		}
		in := ams.TcpInstruction{}
		err = json.Unmarshal(bs, &in)
		if err != nil {
			continue
		}
		c.inCh <- in
	}
}

// networkChange 监听网络变化
func (c *Client) networkChange() {
	for {
		select {
		case addr := <-c.tryCh:
			logrus.Infof("网络变化，重播成功：[%s]", addr)
			// 避免不明原因导致的网络变化过快，部分策略和网络变化绑定，高频切换可能会出现一些奇奇怪怪的问题，
			if time.Now().Unix()-c.lastTime < 2 {
				logrus.Infof("网络变化过快，忽略：[%s]", addr)
				continue
			}
			c.lastTime = time.Now().Unix()
			logrus.Debugf("网络变化时触发逻辑，client:[%v]", c)
			// 网络重连时执行部分策略
			Handler.ExecPolicyForEvent("dial")
			c.Ping(2)
		}
	}
}

// Writer 发送消息
func (c *Client) Writer(in ams.TcpResponse) {
	in.ClientTime = time.Now().Unix()
	in.UniqueCode = c.UniqueCode
	in.ConnId = c.connId
	c.outCh <- in
}

// Ping 有ID就发送ping，没有ID就举手,
func (c *Client) Ping(s int64) {
	time.Sleep(time.Duration(s) * time.Second)
	if c.ClientId == 0 {
		var (
			inst ams.PluginInstruct
			err  error
		)
		err = json.Unmarshal([]byte(dao.HandsUpArgus), &inst)
		if err != nil {
			logrus.Debugf("format HandsUpArgus:[%s],err:[%v]", dao.HandsUpArgus, err)
			c.Writer(ams.TcpResponse{
				ResCode: ams.ResCode.GetHandsUpArgus,
			})
			return
		}
		m, err := Handler.GetHandUpInfo(inst)
		if err != nil {
			logrus.Errorf("GetHandUpInfo err:[%v]", err)
			return
		}
		fileCode, dbCode, nowCode, err := Handler.getUniqueCode(m.BiosSerial, m.HardDiskSerial)
		if err != nil {
			logrus.Errorf("getUniqueCode err:[%v],UniqueType:[%s]", err, dao.UniqueType)
			return
		}
		//todo ,三码不同，可能需要上报服务器
		c.UniqueCode = fileCode
		m.UniqueForFile = fileCode
		m.UniqueForDB = dbCode
		m.UniqueForNow = nowCode
		c.Writer(ams.TcpResponse{
			ResCode:    ams.ResCode.HandsUp,
			ResContent: utils.Obj2Str(m),
		})
	} else {
		c.Writer(ams.TcpResponse{
			ResCode: ams.ResCode.Ping,
		})
	}
}
